﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xeralabs.Collections.Sorted;
using Xeralabs.Timers;
using Xeralabs.Core.Entities;
using Xeralabs.Core.Events;

namespace Xeralabs.Core.Kernel
{
    public delegate void FrameEventHandler(ulong frame, double delay);
    
    internal class Dispatcher : IDisposable
    {
        #region Event Queue
        private class EventTimeComparer : IComparer<GDESKEvent>
        {
            public int Compare(GDESKEvent x, GDESKEvent y)
            {
                if (x.Time > y.Time)
                    return 1;
                else if (x.Time < y.Time)
                    return -1;
                else
                {
                    if (x.Priority < y.Priority)
                        return -1;
                    else if (x.Priority > y.Priority)
                        return 1;
                    else
                        return 0;
                }
            }
        }

        private OrderedSet<GDESKEvent> _eventTimeQueue;

        internal void EnqueueEvent(GDESKEvent item)
        {
            item.SetTime(_systemTimer.GetTime());

            lock (_eventTimeQueue)
            {
                if (this._eventTimeQueue.Add(item))
                {
                    Monitor.Pulse(_eventTimeQueue);
                }
            }
        }
        #endregion

        #region Dispatcher Loop
        private bool _running;
        private bool _disposed;

        private Thread _dispatcherThread;
        private HiResTimer _systemTimer;

        internal void Start()
        {
            _currentTime = 0;
            _dispatcherThread.Start();
        }

        private void DispatcherLoop()
        {
            _running = true;
            _systemTimer = new HiResTimer();
            _systemTimer.Start();
            while (_running)
            {
                GDESKEvent evt = null;
                lock (_eventTimeQueue)
                {
                    if (_eventTimeQueue.Count == 0)
                        Monitor.Wait(_eventTimeQueue);

                    if (_eventTimeQueue.GetFirst().Time <= _systemTimer.GetTime())
                        evt = _eventTimeQueue.RemoveFirst();
                }
                if (evt != null)
                {
                    EntityManager.GetSingleton()[evt.Reciver].ReciveEvent(evt);
                }
                UpdateTime(_systemTimer.GetElapsedTime());
            }
            _systemTimer.Stop();
        }

        internal void Stop()
        {
            _running = false;
        }
        #endregion

        #region Frame Clock
        private int _frameRate;
        private ulong _frameCount;
        private double _frameMs;
        private double _currentTime;

        internal event FrameEventHandler Tick;
        internal event FrameEventHandler FrameEnd;

        internal void UpdateTime(double elapsedTime)
        {
            _currentTime += elapsedTime;

            if (_currentTime >= _frameMs)
            {
                //Do Frame Cleanup (in first frame too ;))
                if (FrameEnd != null)
                    this.FrameEnd.Invoke(_frameCount, _currentTime);
                _currentTime -= _frameMs;
                _frameCount++;
                if (Tick != null)
                    this.Tick.Invoke(_frameCount, _currentTime);
            }
        }
        #endregion

        internal Dispatcher(int frameRate)
        {
            _dispatcherThread = new Thread(new ThreadStart(DispatcherLoop));
            _eventTimeQueue = new OrderedSet<GDESKEvent>(new EventTimeComparer());
            _frameRate = frameRate;
            _frameMs = 1000.0 / _frameRate;
        }

        ~Dispatcher()
        {
            Dispose(false);
        }

        internal void Recycle(bool forced)
        {
            XGDESK instance = XGDESK.GetInstance();
            int target = 0;
            while (_eventTimeQueue.Count > target)
            {
                if (forced)
                {
                    GDESKEvent e = _eventTimeQueue.RemoveFirst();
                    instance.RecycleEvent(e);
                    e = null;
                }
                else
                {
                    //TODO: conditions to recycle.
                    if (_eventTimeQueue[target] == null || _eventTimeQueue[target].Priority < (byte)Priority.System)
                    {
                        GDESKEvent e = _eventTimeQueue[target];
                        if (_eventTimeQueue.Remove(e) && e != null)
                        {
                            instance.RecycleEvent(e);
                            e = null;
                        }
                    }
                    else
                        target++;
                }
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    // managed
                }
                // unmanaged
                _disposed = true;
            }
        }
    }
}